package xyz.lwm.lazycat.jdkhttp;

import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import xyz.lwm.lazycat.jdkhttp.multipart.MultipartFormData;
import xyz.lwm.lazycat.jdkhttp.multipart.UploadSetting;
import xyz.lwm.lazycat.utility.CustomKeyMap;
import xyz.lwm.lazycat.utility.IoUtil;
import xyz.lwm.lazycat.utility.It;
import xyz.lwm.lazycat.utility.StringUtil;
import xyz.lwm.lazycat.utility.exception.IoRuntimeException;
import xyz.lwm.lazycat.web.handle.ContentType;
import xyz.lwm.lazycat.web.handle.HandlerType;
import xyz.lwm.lazycat.web.handle.Header;
import xyz.lwm.lazycat.web.handle.HttpRequest;
import xyz.lwm.lazycat.web.settings.FileUploadSetting;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpCookie;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static xyz.lwm.lazycat.jdkhttp.JdkConstants.MEMORY_SIZE;
import static xyz.lwm.lazycat.jdkhttp.JdkConstants.TMP_UPLOAD_PATH;

/**
 * 参考 hutool
 */
class JdkHttpRequest implements HttpRequest {

    public static final Pattern CHARSET_PATTERN = Pattern.compile("charset\\s*=\\s*([a-z0-9-]*)", Pattern.CASE_INSENSITIVE);

    private final HttpExchange httpExchange;

    private final FileUploadSetting fileUpload;

    private Map<String, HttpCookie> cookieCache;

    private Map<String, List<String>> paramsCache;

    private Map<String, String> pathParamsCache;

    private MultipartFormData multipartFormDataCache;

    private Charset charsetCache;

    private byte[] bodyCache;

    JdkHttpRequest(HttpExchange httpExchange, FileUploadSetting fileUpload) {
        this.httpExchange = httpExchange;
        this.fileUpload = fileUpload;
    }

    @Override
    public String getMethod() {
        return this.httpExchange.getRequestMethod();
    }

    @Override
    public URI getURI() {
        return this.httpExchange.getRequestURI();
    }

    @Override
    public String getPath() {
        return getURI().getPath();
    }

    @Override
    public String getQuery() {
        return getURI().getQuery();
    }

    private Headers getHttpHeaders() {
        return this.httpExchange.getRequestHeaders();
    }

    @Override
    public Map<String, List<String>> getHeaders() {
        return getHttpHeaders();
    }

    @Override
    public String getHeader(String headerKey) {
        return getHttpHeaders().getFirst(headerKey);
    }

    @Override
    public String getHeader(Header headerKey) {
        return getHeader(headerKey.toString());
    }

    @Override
    public String getHeader(String headerKey, Charset charset) {
        final String header = getHeader(headerKey);
        if (null != header) {
            byte[] bytes = header.getBytes(StandardCharsets.ISO_8859_1);
            return new String(bytes, charset);
        }
        return null;
    }

    @Override
    public String getContentType() {
        return getHeader(Header.CONTENT_TYPE);
    }

    @Override
    public Charset getCharset() {
        if (this.charsetCache == null) {
            It.of(getContentType()).filter(StringUtil::isNotBlank)
                    .let(CHARSET_PATTERN::matcher).filter(Matcher::find)
                    .let(matcher -> matcher.group(1))
                    .let(this::parseCharset)
                    .orElse(Charset.defaultCharset())
                    .also(charset -> this.charsetCache = charset);
        }
        return this.charsetCache;
    }

    private Charset parseCharset(String charset) {
        try {
            return Charset.forName(charset);
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public String getUserAgent() {
        return getHeader(Header.USER_AGENT);
    }

    @Override
    public String getCookiesString() {
        return getHeader(Header.COOKIE);
    }

    @Override
    public Collection<HttpCookie> getCookies() {
        return getCookieMap().values();
    }

    @Override
    public Map<String, HttpCookie> getCookieMap() {
        if (null == this.cookieCache) {
            String cookiesString = getCookiesString();
            if (StringUtil.isBlank(cookiesString)) {
                this.cookieCache = Collections.emptyMap();
            } else {
                this.cookieCache = HttpCookie.parse(cookiesString)
                        .stream().collect(Collectors.toMap(
                                HttpCookie::getName,
                                e -> e,
                                (v1, v2) -> v1,
                                () -> new CustomKeyMap<>(String::toLowerCase)));
            }
        }
        return cookieCache;
    }

    @Override
    public HttpCookie getCookie(String cookieName) {
        return getCookieMap().get(cookieName);
    }

    @Override
    public boolean isMultipart() {
        if (!HandlerType.POST.name().equalsIgnoreCase(getMethod())) {
            return false;
        }
        return It.of(getContentType()).filter(StringUtil::isNotBlank)
                .let(ContentType::isMultipart).getOr(false);
    }

    @Override
    public boolean isForUrlencoded() {
        if (!HandlerType.POST.name().equalsIgnoreCase(getMethod())) {
            return false;
        }
        return It.of(getContentType()).filter(StringUtil::isNotBlank)
                .let(ContentType::isFormUrlEncode).getOr(false);
    }

    @Override
    public boolean isApplicationJson() {
        return It.of(getContentType()).filter(StringUtil::isNotBlank)
                .let(ContentType::isApplicationJson).getOr(false);
    }

    @Override
    public boolean isTextPlain() {
        return It.of(getContentType()).filter(StringUtil::isNotBlank)
                .let(ContentType::isTextPlain).getOr(false);
    }

    @Override
    public String getBody() {
        return getBody(getCharset());
    }

    @Override
    public String getBody(Charset charset) {
        return new String(getBodyBytes(), charset);
    }

    @Override
    public byte[] getBodyBytes() {
        if (this.bodyCache == null) {
            this.bodyCache = IoUtil.readBytes(getBodyStream(), true);
        }
        return this.bodyCache;
    }

    @Override
    public InputStream getBodyStream() {
        return this.httpExchange.getRequestBody();
    }

    @Override
    public String getParam(String name) {
        return It.of(getParams(name))
                .let(list -> list.get(0))
                .getOr(null);
    }

    @Override
    public List<String> getParams(String name) {
        return getParams().get(name);
    }

    @Override
    public Map<String, List<String>> getParams() {
        if (this.paramsCache == null) {
            this.paramsCache = new HashMap<>();
            //解析URL中的参数
            String query = getQuery();
            if (StringUtil.isNotBlank(query)) {
                this.paramsCache.putAll(JdkParamUtil.parseParams(getQuery(), getCharset()));
            }

            // is multipart
            if (isMultipart()) {
                this.paramsCache.putAll(getMultipart().getParamListMap());

            } else if (isForUrlencoded()) {
                // 解析body中的参数
                final String body = getBody();
                if(StringUtil.isNotBlank(body)){
                    this.paramsCache.putAll(JdkParamUtil.parseParams(body, getCharset()));
                }
            }
        }

        return this.paramsCache;
    }

    @Override
    public String getPathParam(String name) {
        return getPathParams().get(name);
    }

    @Override
    public Map<String, String> getPathParams() {
        if (pathParamsCache == null) {
            pathParamsCache = new LinkedHashMap<>();
        }
        return pathParamsCache;
    }

    @Override
    public MultipartFormData getMultipart() {
        if(this.multipartFormDataCache == null){
            this.multipartFormDataCache = parseMultipart();
        }
        return this.multipartFormDataCache;
    }

    private MultipartFormData parseMultipart() throws IoRuntimeException {

        UploadSetting uploadSetting = new UploadSetting();
        It.of(fileUpload).let(FileUploadSetting::getMemorySize)
                .filter(i -> i >= MEMORY_SIZE).orElse(MEMORY_SIZE)
                .also(uploadSetting::setMemoryThreshold);
        It.of(fileUpload).let(FileUploadSetting::getTmpUploadPath)
                .filter(StringUtil::isNotBlank).orElse(TMP_UPLOAD_PATH)
                .also(uploadSetting::setTmpUploadPath);

        final MultipartFormData formData = new MultipartFormData(uploadSetting);
        try {
            formData.parseRequestStream(getBodyStream(), getCharset());
        } catch (IOException e) {
            throw new IoRuntimeException(e);
        }
        return formData;

    }

}
